#pragma once

#include <iostream>
#include <memory>
#include <functional>
#include "Epoller.hpp"
#include "InetAddr.hpp"
#include "Socket.hpp"
#include "Log.hpp"
#include "Connection.hpp"
#include "Protocol.hpp"
#include "Reactor.hpp"

using func_t = std::function<std::string(std::string &)>;

// IOService也是Connection的一个子类
// Listener对应的是监听套接字，
// IOService对应的是通信套接字
class IOService : public Connection
{
    static const int size = 1024;

public:
    IOService(int sockfd)
    {
        // 下面的三个函数都是Connection的成员函数，由于IOService是Connection的子类
        // 因此可以直接调用Connection的成员函数
        // 我们要调用这些成员函数，完成IOService这个子类从connection父类继承下来的那些成员变量的初始化
        // 1. 设置文件描述符非阻塞
        SetNonBlock(sockfd);
        SetSockfd(sockfd);
        SetEvents(EPOLLIN | EPOLLET);

    }

    // 这个接口的作用是用来向对端主机发送数据的
    virtual void Sender() override
    {
        // UpdateTime();
        // 直接写
        while (true)
        {
            // 在调用这个函数之前，用户已经将要发送的数据，通过OutString()接口，放到了IOService的成员变量outbuffer中
            // OutString()和outbuffer都是IOService从父类继承下来的
            // 现在我们就需要负责将outbuffer中的数据，发送给对端主机
            ssize_t n = send(Sockfd(), OutString().c_str(), OutString().size(), 0);
            if (n > 0)
            {
                // 走到这里说明，数据发送成功了
                // 但是这时候n也不一定等于OutString().c_str()
                // 也就是说虽然发送成功了，但并不一定发完了。
                // 因为通信套接字的发送缓冲区中剩余的空间。不一定能盛下outbuffer中的所有数据
                // 因此send返回的n，是实际写入套接字的发送缓冲区中的数据大小

                // 既然这个数据已经成功的从用户区的发送缓冲区（IOService的outbuffer）传递到了内核区的发送缓冲区（通信套接字的发送缓冲区）中
                // 那我们就要及时的将用户区中这些发送成功的数据从outbuffer中移除
                DisCardOutString(n); // 移除N个
            }
            else if (n == 0)
            {
                break;
            }
            else
            {
                // 走到这里说明，n=-1，即数据发送失败了
                // 失败的原因有很多种，此时需通过 errno 确定具体错误原因：
                // 我们要根据不同的原因，来进行不同的处理
                // errno = EAGAIN / EWOULDBLOCK：
                // 套接字为非阻塞模式（O_NONBLOCK），当前缓冲区已满或暂时无法发送，可稍后重试。     
                // errno = EPIPE：
                // 连接已关闭（对方发送了 FIN 或 RST），继续发送会导致此错误，通常需关闭本地套接字。
                // errno = ECONNRESET：
                // 对方强制关闭连接（如 RST 包），连接已不可用。
                // errno = EINTR：发送过程被信号中断，可根据需求决定是否重试。
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    // 走到这里说明是内核区中的发送缓冲区写满了，下次再来
                    break;
                }
                else if (errno == EINTR)
                {
                    // 走到这里说明刚刚send发送过程被信号中断，我们就continue再send一次
                    continue;
                }
                else
                {
                    // 调用我们父类的异常处理函数统一处理
                    Excepter();
                    return;
                } 
            }
        }

        // 正常来说，前面的while循环是一个死循环。那么程序应该不会执行到这里。
        // 但是我们在while循环中设置了两处break，如果满足了对应的条件，程序就会到这里。
        //这俩条件分别是：1.n=0 ，说明outbuffer为空，里面没数据可发了
        //2.内核区中套接字文件的发送缓冲区被写满了，没地方写了

        if(!IsOutBufferEmpty())
        {
            // 走到这里说明outbuffer不为空，里面还有数据可发
            // 那原因只能是内核套接字文件的发送缓冲区写满了，没地方写了
            // 既然都写满了，那我们只能等系统把这个套接字文件的发送缓冲区中的数据发出去，把地方腾出来了
            // 这时候我就要告诉系统。啥时候这个通信套接字的发送缓冲区能写了，你通知我一声。
            // 因此我们就要将写事件添加到我们当前链接的监控事件中
            GetOwner()->EnableReadWrite(Sockfd(), true, true);
            // 为什么会出现内核发送缓冲区被写满，这里面的数据都没有发出的情况呢？
            // 数据从本地主机发送缓冲区到对端主机接收缓冲区的完整过程，是由操作系统内核和硬件设备（网卡） 协同完成的
            // 之所以会出现阻塞，很可能就是网络出现了拥塞，也可能是对面主机的接收窗口太小，还有可能是对面把链接断开了
            // 这些都有可能会导致数据在发送缓冲区中堵住发不出去
        }
        else{
            // 走到这里说明我们的outbuffer为空，里面没数据可发了，说明内核发送缓冲区中的数据也被清空了
            // 这时候我们要及时地将写事件从我们当前链接的监控事件中移除
            GetOwner()->EnableReadWrite(Sockfd(), true, false);
        }
    }
    
    // 这个接口的作用是用来从对端主机接收数据的
    virtual void Recver() override
    {
        // UpdateTime();
        // 1. 读取所有数据
        while (true) // ET模式
        {
            char buffer[size];
            // 对端将数据从他的发送缓冲区发送到我们的接受缓冲区这一过程，由操作系统内核和硬件设备（网卡） 协同完成，这个我们不用管
            // 现在假如说对端发来的数据已经成功地传到了我们的内核缓冲区中
            // 此时我们就要调用recv，将内核缓冲区中的数据读到我们用户区的堆区空间buffer中
            ssize_t s = recv(Sockfd(), buffer, sizeof(buffer) - 1, 0); // 非阻塞
            if (s > 0)
            {
                buffer[s] = 0;
                // 读取成功了，把收到的数据，添加到当前连接的接收缓冲区（IOService 的_inbuffer）中
                Append(buffer);
            }
            if (s == 0)
            {
                // 对端关闭连接
                Excepter();
                return;
            }
            else
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    // 当 recv() 系统调用返回 -1 且 errno 为 EAGAIN 或 EWOULDBLOCK 时
                    // 表示当前内核接受缓冲区中没有数据可读取，但并非错误，这是非阻塞模式套接字的典型行为。
                    break;
                }
                else if (errno == EINTR)
                {
                    continue;
                }
                else
                {
                    // 发生错误了
                    Excepter();
                    return;
                }
            }
        }
        
        // 正常来说，前面的while循环是一个死循环。那么程序应该不会执行到这里。
        // 但是我们在while循环中设置了一处break，如果满足了对应的条件，程序就会到这里。
        // 这条件就是 当前内核接受缓冲区中没有数据可读取

        // 所以走到这里时说明，我已经把这个套接字文件的接收缓冲区中的数据全部读到当前IOService 的_inbuffer中了
        // 简而言之，现在内核接收缓冲区已经被我读空了
        std::cout << "outbuffer: \n"
                  << InBuffer() << std::endl;
        // 你能确保你读到的消息，就是一个完整的报文吗？？不能！！！
        // 我们怎么知道，读到了完整的请求呢？？协议！！！
        std::string result;
        if (_on_message)// 如果我们注册了
            result = _on_message(InBuffer()); // 
        // 这个result是我们服务器经过协议解析任务处理，构建好的响应报文

        // 将我们构建好的响应报文，添加到我们的发送缓冲区中，再发回去
        AppendToOut(result);

        // 将我们的响应报文发回去
        // 如何处理写的问题, outbuffer 发送给对方的问题！
        if (!IsOutBufferEmpty())
        {
            // 走到这里说明发送缓冲区不为空
            //方案一: Sender(); // 直接发送, 推荐做法
            //方案二: 使能Writeable即可.
            GetOwner()->EnableReadWrite(Sockfd(), true, true);
        }

    }
    virtual void Excepter() override
    {
        // IO读取的时候，所有的异常处理，全部都会转化成为这个一个函数的调用
        // 出现异常，我们怎么做？？？
        // 打印日志，差错处理，关闭连接，Reactor异常connection, 从内核中，移除对fd的关心
        LOG(LogLevel::INFO) << "客户端连接可能结束，进行异常处理: " << Sockfd();
        GetOwner()->DelConnection(Sockfd());
    }
    // 通信套接字的消息处理函数是用来处理客户端发来的数据的
    // 这个不同协议的处理方法也不一样，因此我们要自己注册
    // 具体来说，这个IOService对象的_on_message是在Listener.hpp中的Recever函数，创建出新通信套接字后注册的
    void RegisterOnMessage(func_t on_message)
    {
        _on_message = on_message;
    }
    ~IOService()
    {
    }

private:
    func_t _on_message;
};